﻿using System;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using Timer = System.Timers.Timer;

namespace LogikBug.Async.Tasks
{
	/// <summary>
	/// Provides a context in which an <see cref="Agent"/> executes.
	/// </summary>
	public class AgentContext : DisposableObject
	{
		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="AgentContext"/>
		/// class with the provided <see cref="Agent"/> and the owning
		/// <see cref="AgentController"/> using the default <see cref="AgentSettings"/>.
		/// </summary>
		/// <param name="agent">The <see cref="Agent"/> which
		/// this will provide a context in which to execute.</param>
		/// <param name="controller">The <see cref="AgentController"/>
		/// who started the execution of the <paramref name="agent"/></param>
		public AgentContext(Agent agent, AgentController controller) : this(new AgentSettings(), agent, controller)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AgentContext"/>
		/// class with the provided <see cref="Agent"/> and the owning
		/// <see cref="AgentController"/> using the provided <see cref="AgentSettings"/>.
		/// </summary>
		/// <param name="settings">The <see cref="AgentSettings"/>
		/// used to initialize the current instance.</param>
		/// <param name="agent">The context in which the
		/// <see cref="Agent"/> is executing.</param>
		/// <param name="controller">The <see cref="AgentController"/>
		/// who started the execution of the <paramref name="agent"/></param>
		public AgentContext(AgentSettings settings, Agent agent, AgentController controller)
		{
			Agent = agent;
			agent.Context = this;
			Controller = controller;
			AgentSettings = settings;
			Timer = CreateTimer();

			if (!settings.UseThreadPool)
			{
				Thread = new Thread(agent.Activate);
			}
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the <see cref="Thread"/> on which
		/// the current <see cref="Agent"/> is executing.
		/// </summary>
		public Thread Thread { get; set; }

		/// <summary>
		/// The <see cref="Agent"/> that is currently executing.
		/// </summary>
		public Agent Agent { get; private set; }

		/// <summary>
		/// Gets the <see cref="Timer"/> associated with the
		/// current <see cref="Agent"/>. The <see cref="Timer"/>
		/// manages when the <see cref="Agent"/> should expire.
		/// </summary>
		public Timer Timer { get; private set; }

		/// <summary>
		/// Gets the <see cref="AgentController"/>
		/// who owns the <see cref="Agent"/>.
		/// </summary>
		public AgentController Controller { get; private set; }

		/// <summary>
		/// Gets the <see cref="AgentSettings"/>
		/// associated with the current <see cref="Agent"/>.
		/// </summary>
		public AgentSettings AgentSettings { get; private set; }

		#endregion

		#region Methods

		private Timer CreateTimer()
		{
			var timer = new Timer(AgentSettings.ExecutionTimeout.TotalMilliseconds) {AutoReset = false};
			timer.Elapsed += ExecutionTimeout;
			return timer;
		}

		/// <summary>
		/// The event the <see cref="Timer"/> calls when when
		/// the time has elapsed and the agent should expire.
		/// </summary>
		/// <param name="state">Not used.</param>
		/// <param name="e">Not used.</param>
		private void ExecutionTimeout(object state, ElapsedEventArgs e)
		{
			var timer = Timer;

			if (timer != null)
			{
				Timer.Stop();
				Timer.Close();
			}

			if (Thread != null)
			{
				Thread.Abort();
			}

			Agent.InvokeErrored(new TimeoutException(Agent.GetTimeoutMessage()));
		}

		/// <summary>
		/// Disposes the resources associated with this current instance
		/// and calls dispose on the current <see cref="Agent"/> executing.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (Timer != null)
				{
					var timer = Timer;
					timer.Stop();
					timer.Close();
					timer.Dispose();
					Timer = null;
				}

				if (Agent != null)
				{
					Agent.Dispose();
					Agent = null;
				}

				if (Thread != null)
				{
					Thread = null;
				}
			}
		}

		/// <summary>
		/// Starts the execution of the
		/// <see cref="Agent"/> on the <see cref="Thread"/>.
		/// </summary>
		public void Start()
		{
			Timer.Start();

			if (AgentSettings.UseThreadPool)
			{
				ThreadPool.QueueUserWorkItem(o => Agent.Activate());
			}
			else
			{
				Task.Factory.StartNew(Agent.Activate);
			}
		}

		/// <summary>
		/// This method is invoked when the parent
		/// <see cref="AgentController"/> is disposing.
		/// </summary>
		public void ControllerDispose()
		{
			Dispose();
		}

		#endregion
	}
}